﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ProtocoloPropietario;

namespace Cliente
{
    class TransferFile
    {
        private string ruta;
        private Servidor servidor;
        private IPAddress ipCliente;
        private int puertoCliente;
        private Socket socCliente;
        private Socket socServidor;

        public string Ruta
        {
            get { return ruta; }
            set { ruta = value; }
        }

        public TransferFile(string rutaArchivo, Servidor serv, IPAddress ipC, int numP)
        {
            servidor = serv;
            ruta = rutaArchivo;
            ipCliente = ipC;
            puertoCliente = numP;
        }

        public bool Subir()
        {
            FileStream fileASubir = new FileStream(ruta, FileMode.Open, FileAccess.Read);
            IPEndPoint ipEnd = new IPEndPoint(servidor.Ip, servidor.NumPuerto);
            socCliente = new Socket(ipEnd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            socServidor = new Socket(ipEnd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                using (fileASubir)
                {
                    //creo el MD5
                    MD5 md5 = new MD5CryptoServiceProvider();
                    byte[] md5Buff = md5.ComputeHash(fileASubir);
                    string textoMD5 = (System.Text.Encoding.ASCII.GetString(md5Buff, 0, md5Buff.Length));

                    //Mando el primerprotocolo para conectar con el nombre y el MD5
                    FileInfo fi = new FileInfo(fileASubir.Name);
                    long tamanioArchivo = fi.Length;
                    string strMsj = fi.Name + Protocolo.SEPARADOR + fi.Length + Protocolo.SEPARADOR + textoMD5;
                    MensajeProtocolo msj = new MensajeProtocolo(Protocolo.Header.REQ, Protocolo.Comando.CLIENTE_SUBIDA, strMsj.Length,strMsj);
                    byte[] buff = msj.ConvertirAByte();
                    socCliente.Connect(ipEnd);
                    socCliente.Send(buff, buff.Length, SocketFlags.None);

                    IPEndPoint ipEndAEscuchar = new IPEndPoint(ipCliente, puertoCliente);
                    socServidor.Bind(ipEndAEscuchar);
                    socServidor.Listen(5);
                    socCliente = socServidor.Accept();

                    fileASubir.Position = 0;
                    //lee segun el tamanio del protocolo
                    int tamDatos = 10000;
                    buff = new byte[tamDatos];
                    int readCount = fileASubir.Read(buff, 0, tamDatos);//Cambiar a ancho de protocolo cuando este
                    Thread.Sleep(250);
                    Console.WriteLine("Enviando.....( " + 0 + "% )");
                    int tiempoUltimaEspera = Environment.TickCount;
                    int cantBytes = 0;
                    int n = 0;
                    while (readCount > 0)
                    {
                        MensajeProtocolo msjAEnviar = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_SUBIDA, readCount, buff);
                        byte[] protocoloAEnviar = msjAEnviar.ConvertirAByte();
                        socCliente.Send(protocoloAEnviar);
                        cantBytes += buff.Length;

                        if (Throttling(tiempoUltimaEspera, protocoloAEnviar.Length))
                        {
                            tiempoUltimaEspera = Environment.TickCount;
                        }

                        byte[] buffRespuesta = new byte[11];
                        socCliente.Receive(buffRespuesta);
                        MensajeProtocolo msjRes = MensajeProtocolo.ObtenerMensajeProtocolo(buffRespuesta);

                        if (msjRes.Largo == 1)
                        {
                            if (tamanioArchivo < cantBytes + buff.Length && (tamanioArchivo - cantBytes) > 0)
                            {
                                buff = new byte[tamanioArchivo - cantBytes];
                                tamDatos = (int)tamanioArchivo - cantBytes;
                            }
                            else
                            {
                                buff = new byte[tamDatos];
                            }
                            readCount = fileASubir.Read(buff, 0, tamDatos);
                        }
                        else
                        {
                            cantBytes -= buff.Length;
                        }
                        n++;
                        if (n == 5) n = 0;
                        if (n == 0) Console.WriteLine("Enviando.....( " + cantBytes * 100 / tamanioArchivo + "% )");



                    }
                    Console.WriteLine("Enviando.....( 100% )");
                    buff = new byte[12];
                    socCliente.Receive(buff);
                    MensajeProtocolo msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);
                    List<string> infoRes = msjRecibido.InfoDatos();
                    socCliente.Close();
                    socServidor.Close();
                    fileASubir.Close();
                    if (infoRes[0].Equals("1"))
                    {
                        return true;
                    }
                    else
                    {
                        Console.WriteLine("El archivo se ha subido incorrectamente.");
                        return false;
                    }
                }

            }
            catch (FileNotFoundException)
            {
                socCliente.Close();
                fileASubir.Close();
                socServidor.Close();
                return false;
            }
            catch (SocketException)
            {
                socCliente.Close();
                socServidor.Close();
                fileASubir.Close();
                return false;
            }
        }

        public int Bajar(int pos)
        {
            if (pos > 0)
            {
                IPEndPoint ipEndServidor = new IPEndPoint(servidor.Ip, servidor.NumPuerto);
                socServidor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socCliente = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socCliente.Connect(ipEndServidor);

            }
            int bytes = pos;
            try
            {
                MensajeProtocolo msjAEnviar;
                if (pos == 0)
                {
                    msjAEnviar = new MensajeProtocolo(Protocolo.Header.REQ, Protocolo.Comando.CLIENTE_BAJADA_INICIO, ruta.Length, ruta);
                }
                else
                {
                    msjAEnviar = new MensajeProtocolo(Protocolo.Header.REQ, Protocolo.Comando.CLIENTE_BAJADA_POS, ruta.Length, ruta);
                }
                byte[] buff = msjAEnviar.ConvertirAByte();

                socCliente.Send(buff, buff.Length, SocketFlags.None);
                if (pos > 0)
                {
                    IPEndPoint ipEndAEscuchar = new IPEndPoint(ipCliente, puertoCliente);
                    socServidor.Bind(ipEndAEscuchar);
                    socServidor.Listen(5);
                socCliente = socServidor.Accept();
                }
                buff = new byte[30];
                socCliente.Receive(buff);
                MensajeProtocolo msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);
                List<string> infoRes = msjRecibido.InfoDatos();
                long largo = long.Parse(infoRes[0]);
                ruta = Program.rutaCarpetaArchivos + "\\" + ruta;
                Console.WriteLine("Descargando.....( 0% )");
                FileStream fsNew = new FileStream(ruta, FileMode.Create, FileAccess.Write);
                using (fsNew)
                {
                    int tamanioBuff = 10010;
                    int n = 0;
                    do
                    {
                        if (largo < tamanioBuff - 10)
                        {
                            buff = new byte[largo + 10];
                        }
                        else
                        {
                            buff = new byte[tamanioBuff];
                        }
                        socCliente.Receive(buff);
                        MensajeProtocolo msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_POS, 1, "");
                        if (MensajeProtocolo.ValidarMensaje(buff))
                        {
                            msjRecibido = MensajeProtocolo.ObtenerMensajeProtocolo(buff);

                            byte[] datosAEscribir = msjRecibido.Datos;
                            bytes += msjRecibido.Largo;
                            fsNew.Write(datosAEscribir, 0, msjRecibido.Largo);

                        }
                        else
                        {
                            msjRespuesta = new MensajeProtocolo(Protocolo.Header.RES, Protocolo.Comando.CLIENTE_BAJADA_POS, 0, "");
                        }
                        socCliente.Send(msjRespuesta.ConvertirAByte()); n++;
                        if (n == 5) n = 0;
                        if (n == 0) 
                            Console.WriteLine("Descargando.....( " + bytes * 100 / largo + "% )");
                    }
                    while (bytes < largo);

                }
                Console.WriteLine("Descargando.....( 100% )");
                socCliente.Close();
                socServidor.Close();
                int ret = -1;
                return ret;
            }
            catch (FileNotFoundException)
            {
                socCliente.Close();
                socServidor.Close();
                return bytes;
            }
            catch (SocketException)
            {
                socCliente.Close();
                socServidor.Close();
                return bytes;
            }
        }

        public List<string> ObtenerNombresArchivos()
        {
            IPEndPoint ipEndServidor = new IPEndPoint(servidor.Ip, servidor.NumPuerto);
            socServidor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socCliente = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socCliente.Connect(ipEndServidor);

            MensajeProtocolo msj = new MensajeProtocolo(Protocolo.Header.REQ, Protocolo.Comando.CLIENTE_BAJADA_INICIO, 0, new byte[0]);
            byte[] b = msj.ConvertirAByte();
            socCliente.Send(b, b.Length, SocketFlags.None);

            IPEndPoint ipEndAEscuchar = new IPEndPoint(ipCliente, puertoCliente);
            socServidor.Bind(ipEndAEscuchar);
            socServidor.Listen(5);
            socCliente = socServidor.Accept();
            byte[] paquete = new byte[300];
            socCliente.Receive(paquete);

            MensajeProtocolo msg = MensajeProtocolo.ObtenerMensajeProtocolo(paquete);
            List<string> listRes = msg.InfoDatos();

            return listRes;
        }


        private bool Throttling(long tiempoUltimaEspera, int cantBytes)
        {
            long milSegTransc = Environment.TickCount - tiempoUltimaEspera;

            if (milSegTransc > 0)
            {

                long bps = cantBytes * 1000L / milSegTransc;

                if (bps > servidor.AnchoDeBanda)
                {
                    // Calcula el tiempo a dormir
                    long tiempoADormirTotal = cantBytes * 1000L / servidor.AnchoDeBanda;
                    int tiempoADormir = (int)(tiempoADormirTotal - milSegTransc);

                    if (tiempoADormir > 1)
                    {
                        try
                        {
                            Thread.Sleep(tiempoADormir);
                        }
                        catch (ThreadAbortException)
                        {

                        }
                        return true;
                    }

                }

            }
            return false;
        }
    }
}
